﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Reflection;
using ExcelDna.ComInterop;
using ExcelDna.ComInterop.ComRegistration;

using HRESULT = System.Int32;
using IID = System.Guid;
using CLSID = System.Guid;
using DWORD = System.UInt32;

namespace ExcelDna.Integration.Rtd
{
    [ClassInterface(ClassInterfaceType.None)]
    internal class RtdServerWrapper : IRtdServer
    {
        // 'ProgId' under which ExcelDna registered the server 
        // - might be the class FullName or might be the ProgIdAttribute.
        readonly string _progId;

        // If the object implements ExcelDna.Integration.Rtd.IRtdServer we call directly...
        readonly IRtdServer _rtdServer;
        // ... otherwise we call via delegates assigned through interface mapping.
        // Private delegate types for the IRtdServer interface ...
        delegate object delConnectData(int topicId, ref Array strings, ref bool newValues);
        delegate void delDisconnectData(int topicId);
        delegate int delHeartbeat();
        delegate Array delRefreshData(ref int topicCount);
        delegate int delServerStart(IRTDUpdateEvent CallbackObject); // Careful - might be an unexpected IRTDUpdateEvent...
        delegate void delServerTerminate();

        // ... and corresponding instances.
        readonly delConnectData _ConnectData;
        readonly delDisconnectData _DisconnectData;
        readonly delHeartbeat _Heartbeat;
        readonly delRefreshData _RefreshData;
        readonly delServerStart _ServerStart;
        readonly delServerTerminate _ServerTerminate;

        public RtdServerWrapper(object rtdServer, string progId)
        {
            // CAREFUL: ProgId passed in might be token used from regular ClassFactory.
            _progId = progId;
            _rtdServer = rtdServer as IRtdServer;
            if (_rtdServer == null)
            {
                // The RtdServer implements another instance of IRtdServer (maybe from some office PIA).
                // We put together some delegates to call through.
                Type[] itfs = rtdServer.GetType().GetInterfaces();
                foreach (Type itf in itfs)
                {
                    if (itf.GUID == ComAPI.guidIRtdServer)
                    {
                        InterfaceMapping map = rtdServer.GetType().GetInterfaceMap(itf);
                        for (int i = 0; i < map.InterfaceMethods.Length; i++)
                        {
                            MethodInfo mi = map.InterfaceMethods[i];
                            switch (mi.Name)
                            {
                                case "ConnectData":
                                    _ConnectData = (delConnectData)Delegate.CreateDelegate(typeof(delConnectData), rtdServer, map.TargetMethods[i]);
                                    break;
                                case "DisconnectData":
                                    _DisconnectData = (delDisconnectData)Delegate.CreateDelegate(typeof(delDisconnectData), rtdServer, map.TargetMethods[i]);
                                    break;
                                case "Heartbeat":
                                    _Heartbeat = (delHeartbeat)Delegate.CreateDelegate(typeof(delHeartbeat), rtdServer, map.TargetMethods[i]);
                                    break;
                                case "RefreshData":
                                    _RefreshData = (delRefreshData)Delegate.CreateDelegate(typeof(delRefreshData), rtdServer, map.TargetMethods[i]);
                                    break;
                                case "ServerStart":
                                    // ServerStart is tricky because of the parameter type mapping.
                                    MethodInfo serverStartMethod = map.TargetMethods[i];
                                    _ServerStart = delegate(IRTDUpdateEvent updateEvent)
                                    {
                                        return (int)serverStartMethod.Invoke(rtdServer, new object[] { updateEvent });
                                    };
                                    break;
                                case "ServerTerminate":
                                    _ServerTerminate = (delServerTerminate)Delegate.CreateDelegate(typeof(delServerTerminate), rtdServer, map.TargetMethods[i]);
                                    break;
                            }
                        }
                    }
                }
            }
        }

        public object ConnectData(int topicId, ref Array strings, ref bool newValues)
        {
            try
            {
                using (XlCall.Suspend())
                {
                    if (_rtdServer != null)
                    {
                        return _rtdServer.ConnectData(topicId, ref strings, ref newValues);
                    }
                    return _ConnectData(topicId, ref strings, ref newValues);
                }
            }
            catch (Exception e)
            {
                Logging.LogDisplay.WriteLine("Error in RTD server {0} ConnectData: {1}", _progId, e.ToString());
                return null;
            }
        }

        public void DisconnectData(int topicId)
        {
            try
            {
                using (XlCall.Suspend())
                {
                    if (_rtdServer != null)
                    {
                        _rtdServer.DisconnectData(topicId);
                        return;
                    }
                    _DisconnectData(topicId);
                }
            }
            catch (Exception e)
            {
                Logging.LogDisplay.WriteLine("Error in RTD server {0} DisconnectData: {1}", _progId, e.ToString());
            }
        }

        public int Heartbeat()
        {
            try
            {
                using (XlCall.Suspend())
                {
                    if (_rtdServer != null)
                    {
                        return _rtdServer.Heartbeat();
                    }
                    return _Heartbeat();
                }
            }
            catch (Exception e)
            {
                Logging.LogDisplay.WriteLine("Error in RTD server {0} Heartbeat: {1}", _progId, e.ToString());
                return 0;
            }
        }

        public Array RefreshData(ref int topicCount)
        {
            try
            {
                using (XlCall.Suspend())
                {
                    if (_rtdServer != null)
                    {
                        return _rtdServer.RefreshData(ref topicCount);
                    }
                    return _RefreshData(ref topicCount);
                }
            }
            catch (Exception e)
            {
                Logging.LogDisplay.WriteLine("Error in RTD server {0} RefreshData: {1}", _progId, e.ToString());
                return null;
            }
        }

        public int ServerStart(IRTDUpdateEvent CallbackObject)
        {
            try
            {
                using (XlCall.Suspend())
                {
                    if (_rtdServer != null)
                    {
                        return _rtdServer.ServerStart(CallbackObject);
                    }
                    // CallbackObject will actually be a RCW (__ComObject) so the type 'mismatch' calling Invoke never arises.
                    return _ServerStart(CallbackObject);
                }
            }
            catch (Exception e)
            {
                Logging.LogDisplay.WriteLine("Error in RTD server {0} ServerStart: {1}", _progId, e.ToString());
                return 0;
            }
        }

        public void ServerTerminate()
        {
            try
            {
                using (XlCall.Suspend())
                {
                    RtdRegistration.UnregisterRTDServer(_progId);
                    if (_rtdServer != null)
                    {
                        _rtdServer.ServerTerminate();
                        return;
                    }
                    _ServerTerminate();
                }
            }
            catch (Exception e)
            {
                Logging.LogDisplay.WriteLine("Error in RTD server {0} ServerTerminate: {1}", _progId, e.ToString());
            }
        }
    }
}
